iT邦幫忙

2023 iThome 鐵人賽

DAY 28
2
Software Development

CRUD仔的一生(上集)系列 第 29

[QUERY] 讀寫分離(Read/Write Splitting)

  • 分享至 

  • xImage
  •  

讀寫分離(Read/Write Splitting)

前言

昨天介紹了虛擬表,尤其是MTV,更是將要Query的資料與Tx完全分離,達到OLAP的目的。
而今天要介紹的是讀寫分離,一樣是將要Query的資料與Tx完全分離,
但這裡乾脆直接複製一份db副本來應付所有Query,而做write動作的tx全都在master db server上。
今天將會簡單的建立一個讀寫分離,稍微提一下實作讀寫分離時所帶來的問題。

原理

還記得之前介紹的WAL機制嗎?DB在寫表之前為了希望資料庫能快點回應我們 Committed,
先把修改的操作都先以日誌的方式存起來,等到比較不忙時,
在將日誌回放(Replay),處理 index,FSM...的,正確的存到 Table 之中。
而這個WAL log就是我們拿來做讀寫分離的關鍵所在。
當wal log新增紀錄,就代表還有資料尚未寫入硬碟之中,
master更新wal log,然後透過同步WAL log的方式讓slave 將資料寫入。
slave透過WAL 回放(Redo,roll-forward recovery)機制,不斷的寫入新的資料。

同步機制

關鍵就是wal log的存取,所以可以有各種花式的存取
可以透過以下兩大類同步wal log的方式來做混和。

  • 存檔式(archive): 我喜歡叫他 Polling,原理大概是 Master 主動將 WAL 複製到一個安全的位置,Slave 定期去抓回來回放。
  • 流式(streaming): 就是所謂的串流傳輸,Master 直接透過 TCP 協議來將 WAL 傳送到 Slave 中。

建構讀寫分離

資料庫主體

要建構一個簡單的系統,最簡單又方便的莫非就是直接寫dokcer-compose了。
這裡使用bitnami/postgresql這個image來手把手的做讀寫分離。

# ./compose.yaml
version: '2'

services:
  postgresql-master:
    image: 'bitnami/postgresql:latest'
    ports:
      - '5432'
    environment:
      - POSTGRESQL_REPLICATION_MODE=master
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=repl_password
      - POSTGRESQL_USERNAME=my_user
      - POSTGRESQL_PASSWORD=my_password
      - POSTGRESQL_DATABASE=my_database
  postgresql-slave:
    image: 'bitnami/postgresql:latest'
    ports:
      - '5432'
    depends_on:
      - postgresql-master
    environment:
      - POSTGRESQL_REPLICATION_MODE=slave
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=repl_password
      - POSTGRESQL_MASTER_HOST=postgresql-master
      - POSTGRESQL_MASTER_PORT_NUMBER=5432
      - POSTGRESQL_PASSWORD=my_password

啟動時,直接docker-compose up即可,
如果想開多個slave,可以加上--scale
像這樣docker-compose up --detach --scale postgresql-master=1 --scale postgresql-slave=3
這樣就成功建立了一個簡單的讀寫分離的環境了,就是這麼簡單。

這裡要特別注意一下,REPLICATION_USER是沒有任何權限的,單純拿來做WAL LOG的同步。
你可以新建一個USER來做READ操作,也可以給予這個帳號READ權限,
或者直接使用POSTGRESQL_USERNAME與POSTGRESQL_PASSWORD來做操作。

連線 

你可以使用docker ps看一下目前node port開在哪裡,
再隨意寫個程式或DB IDE來連線,
看到master建立table,slave也會隨著建立table。
master插入資料,slave也會隨著插入資料。
至於如果你想要在slave做write操作看master會不會隨著write
你可以看到這個錯誤訊息
ERROR: cannot execute INSERT in a read-only transaction

在orm中我們可以自由的選擇使用哪條連線來做tx

dataSource.createQueryRunner("master")

一個tx又讀又寫

有人可能會問,一個tx又讀又寫會發生什麼事情?
該不會一部分sql執行在slave,一部分執行在master吧?這樣怎麼維持acid?

聰明的你一定不會這麼想,因為一個tx只會對應到一條連線,
想當然這整個tx只會選擇master或slave來做連線。
當如果有write操作時,就是直接選master來做該tx。

問題: 讀己之寫問題

我們知道master/slave之間的資料是利用同步wal log的方式來完成,
既然是同步檔案,就一定會有時間差
想像一個情境,如擬更新了你的資訊,
但查看時卻還是舊的資訊,所以你又在更新了一次,點進去看又正常了?
其實有可能就是同步時的時間差。無法讀己之寫。

這個時候我們可以想辦法讓write後的request也讀master的資料,
就可以達到讀己所寫的目的了,不論是紀錄client資訊+debounce time等方式。

結語

其實做讀寫分離並不困難,困難的是在該架構下隨之而來的問題。
這裡只是將較常見的讀己之寫問題寫出,其實不只這些,還有許許多多問題可以探討。
一方面難Debug,另一方面也增加了程式設計的難度,要特別注意有哪些side effect。
這些議題,將會再下次介紹給大家。

參考資料

  1. Designing Data-Intensive Application - Replication
  2. Streaming Replication
  3. bitnami/postgresql
  4. replication

上一篇
[QUERY] 虛擬表(Virtual Table)
下一篇
[QUERY] 分割資料表(Partition/Sharding)
系列文
CRUD仔的一生(上集)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言